package cache_local

import (
	"fmt"
	"gitee.com/kingzyt/common/log"
)

type ICache interface {
	Name() string
	// ignore cache repeated miss
	GetInternal(key interface{}, args ...interface{}) (value interface{}, ok bool)
	Get(key interface{}, args ...interface{}) (value interface{}, ok bool)
	//if ttlS <= 0, means never expire
	SetWithTTL(ttlS int, key interface{}, args ...interface{})
	//if expiredTS <= 0, means never expire
	SetWithExpiredT(expiredTS int64, key interface{}, args ...interface{})
	Set(key interface{}, args ...interface{})
	SetDirty(key interface{}, args ...interface{})
	ForceAllClear()
	NotifyCacheChange(key interface{}, args ...interface{})
}

///////////////////////////////////
var NewCacheWithKeyExpiredTimeS = NewMemCacheWithKeyExpiredTimeS

func DefaultCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 0, false)
}
func OneDayCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 24*3600, false)
}
func OneHourCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 3600, false)
}
func HalfAnHourCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 1800, false)
}
func TenMCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 600, false)
}
func FiveMCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 5*60, false)
}
func OneMCache(name string) ICache {
	return NewCacheWithKeyExpiredTimeS(name, 60, false)
}

/*
TODO 使用消息队列，各个功能需要向消息broker发送某行为发生的事件，然后使用cache的模块需要订阅相关模块的事件来触发cache失效，
	这样cache的更新和失效都可以在一个模块中，而不用分散到各个相关模块，达到解耦的目的
	查询有没有现成的优质本地内存cache系统，没有就用MemCache修改，因为对象无法避免序列化和反序列化且不丢失信息或结构地存储在任意的数据库中，性能无法保证
	redis用于sql查询时间超过秒级，大数据集合的一级缓存；本地缓存甚至可以考虑作为redis的二级缓存
*/
const (
	single_key_repeated_invalid_attack_protection_time_s = 30

	// TODO 研究redisCluster, 看能不能同时满足下面的几个需求
	// 考虑到mysql主从同步有延时, 这样即使写的时候dirty了cache, 也不能保证后续cache更新后获得的就是最新的数据, [需要expire时间尽量短]=>最理想就是,从库写入后,通知更新缓存
	// 缓存key必须在一定时间后过期, 否则可能造成内存不足, 用redis的话, 有淘汰旧key的策略, [需要expire时间适中]
	// 而从缓存的最基本的需求而言, 缓存有效时间越长, 带来的加速收益越大, [需要expire时间尽量长]
	/*
		分布式本地cache，有一个cache失效通知中心来通知所有节点上的本地cache做更新
		本地cache主要用于大批量的数据列表，以及数据结构复杂，且频繁使用的不适合反复marshal、unmarshal来存取redis的数据
		我们所有的写入都不信任cache，只作为前提快速判错使用，所以可以使用中心节点的失效通知来完成读的最终一致性
	*/
	default_key_expired_time_s = 3600 //目前在单机无主从的情况下, 可以稍长一些
)

var ErrCacheRepeatedMiss = fmt.Errorf("repeated cache missing")

// Get is a better version of GetCache, we allow error to happen in the refreshData
func Get(cache ICache, refreshData func() (newV interface{}, err error), key interface{}) (interface{}, error) {
	return getCore(cache, true, refreshData, key)
}
func GetInternal(cache ICache, refreshData func() (newV interface{}, err error), key interface{}) (interface{}, error) {
	return getCore(cache, false, refreshData, key)
}
func getCore(cache ICache, needCacheRepeatedMiss bool, refreshData func() (newV interface{}, err error), key interface{}) (interface{}, error) {
	var v interface{}
	var ok bool
	if needCacheRepeatedMiss {
		v, ok = cache.Get(key)
	} else {
		v, ok = cache.GetInternal(key)
	}
	if ok {
		return v, nil
	}

	newV, err := refreshData()
	if err != nil {
		return nil, err
	}
	if newV != nil {
		cache.Set(key, newV)
	} else {
		log.Info(cache.Name(), log.Trace, "getCore: key:%v, get nil value from refreshData()", key)
	}
	return newV, nil
}
